package pl.edu.agh.neural.input;

import pl.edu.agh.neural.core.*;
import pl.edu.agh.neural.simple.InputConnection;
import pl.edu.agh.neural.simple.SimpleLayer;
import pl.edu.agh.neural.simple.SimpleNeuron;
import pl.edu.agh.neural.simple.activation.IActivationFunction;
import pl.edu.agh.neural.simple.activation.LinearActivation;
import pl.edu.agh.neural.simple.activation.LogisticActivation;
import pl.edu.agh.neural.simple.activation.StepActivation;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class SimpleNetworkBuilder {
	public static INetwork buildNetwork(String filename) throws IOException, FileParsingException {
		File fil = new File(filename);
		FileReader inputFil = new FileReader(fil);
		BufferedReader in = new BufferedReader(inputFil);

		InputLayer inputLayer = createInputLayer(in.readLine());

        List<SimpleLayer> layers = new ArrayList<>();
        ILayer previousLayer = inputLayer;
		for (String line = in.readLine(); line != null; line = in.readLine()) {
			SimpleLayer insideLayer = createInsideLayer(line, previousLayer);
            layers.add(insideLayer);
            previousLayer = insideLayer;
		}

		return new BasicNetwork(inputLayer, layers);
}

	private static SimpleLayer createInsideLayer(String line, ILayer previousLayer)
			throws FileParsingException {
		String[] splitted = line.split(" ");

		String activationFunctionName = splitted[0];

        boolean hasBias = false;
        List<SimpleNeuron> neurons = new ArrayList<>();
		for (int i = 1; i < splitted.length; i++) {

            String neuronString = splitted[i];
            if (neuronString.equals("bias")) {
                hasBias = true;
                continue;
            }
			String[] connValues = splitted[i].split(",");
			SimpleNeuron neuron = createInsideNeuron(activationFunctionName, connValues, previousLayer);
			neurons.add(neuron);
		}

		return new SimpleLayer(neurons, hasBias);
	}

	private static SimpleNeuron createInsideNeuron(String activationFunctionName, String[] connValues, ILayer previousLayer) {

        String activationParameter = connValues[0];
        if (!activationParameter.startsWith("t")) {
            throw new FileParsingException("each neuron description must start with activation function parameter");
        }

        double activationParameterValue = Double.parseDouble(activationParameter.substring(1));
        IActivationFunction activationFunction = getActivationFunction(activationFunctionName, activationParameterValue);

        List<? extends INeuron> previousLayerNeurons = previousLayer.getNeurons();
        InputConnection[] inputConnections = new InputConnection[connValues.length - 1];
        for (int i = 0; i < connValues.length - 1; i++) {
            double weight = Double.parseDouble(connValues[i + 1]);
            INeuron connectedNeuron = previousLayerNeurons.get(i);
            inputConnections[i] = new InputConnection(connectedNeuron, weight);
        }

        return new SimpleNeuron(activationFunction, inputConnections);
	}

	private static IActivationFunction getActivationFunction(String funName, double activationParameter)
			throws FileParsingException {
		if (funName.startsWith("lin"))
			return new LinearActivation(activationParameter);
		if (funName.startsWith("ste"))
			return new StepActivation(activationParameter);
		if (funName.startsWith("sig"))
			return new LogisticActivation(activationParameter);
		throw new FileParsingException("Unknown activation function: " + funName);
	}

	private static InputLayer createInputLayer(String readLine) throws FileParsingException {
		String[] splittedLine = readLine.split(" ");

		if (!splittedLine[0].equals("in"))
			throw new FileParsingException("'in' expected");

        Integer inputsCount = Integer.parseInt(splittedLine[1]);
        List<InputNeuron> inputNeurons = new ArrayList<>(inputsCount);

        for (int i = 0; i < inputsCount; i++) {
            inputNeurons.add(new InputNeuron());
        }

        boolean hasBias = false;
        if (splittedLine.length == 3)
        {
            if (!splittedLine[2].equals("bias"))
                throw new FileParsingException("'bias' expected");

            hasBias = true;
        }

		return new InputLayer(inputNeurons, hasBias);
	}
}
